<script lang="tsx">
import { computed, defineComponent, toRefs, unref } from 'vue';
import type { Ref } from 'vue';
import { Form, Col, Divider } from 'ant-design-vue';
import { cloneDeep, upperFirst } from 'lodash-es';
import { isBoolean, isFunction, isNull } from '@/utils/is';
import { BasicHelp } from '#/components';
import { useItemLabelWidth } from '../hooks/useLabelWidth';
import { basicFormItemProps } from '../props';
import { FormProps, FormSchema } from '../types/form';
import { ValidationRule } from 'ant-design-vue/lib/form/Form';
import { createPlaceholderMessage, setComponentRuleType } from '../helper';
import { getSlot } from '@/utils/helper/tsxHelper';
import { componentMap } from '../componentMap';

export default defineComponent({
	name: 'BasicFormItem',
	inheritAttrs: false,
	props: basicFormItemProps,
	setup(props, { slots }) {
		const { schema, formProps } = toRefs(props) as {
			schema: Ref<FormSchema>;
			formProps: Ref<FormProps>;
		};

		const itemLabelWidthProp = useItemLabelWidth(schema, formProps);

		const getValues = computed(() => {
			const { allDefaultValues, formModel, schema } = props;
			return {
				field: schema.field,
				model: formModel,
				values: {
					...allDefaultValues,
					...formModel,
				} as Recordable,
				schema: schema,
			};
		});

		const getDisable = computed(() => {
			const { disabled: globDisabled } = props.formProps;
			const { dynamicDisabled } = props.schema;
			const { disabled: itemDisabled = false } = unref(getComponentsProps);
			let disabled = !!globDisabled || itemDisabled;
			if (isBoolean(dynamicDisabled)) {
				disabled = dynamicDisabled;
			}
			if (isFunction(dynamicDisabled)) {
				disabled = dynamicDisabled(unref(getValues));
			}
			return disabled;
		});

		function getShow() {
			const { show, ifShow } = props.schema;
			const { showAdvancedButton } = props.formProps;
			const itemIsAdvanced = showAdvancedButton
				? isBoolean(props.schema.isAdvanced)
					? props.schema.isAdvanced
					: true
				: true;

			let isShow = true;
			let isIfShow = true;

			if (isBoolean(show)) {
				isShow = show;
			}
			if (isBoolean(ifShow)) {
				isIfShow = ifShow;
			}
			if (isFunction(show)) {
				isShow = show(unref(getValues));
			}
			if (isFunction(ifShow)) {
				isIfShow = ifShow(unref(getValues));
			}
			isShow = isShow && itemIsAdvanced;
			return { isShow, isIfShow };
		}

		const getComponentsProps = computed(() => {
			const { schema, tableAction, formModel, formActionType } = props;
			let { componentProps = {} } = schema;
			if (isFunction(componentProps)) {
				componentProps = componentProps({ schema, tableAction, formModel, formActionType }) ?? {};
			}
			if (schema.component === 'Divider') {
				componentProps = Object.assign({ type: 'horizontal' }, componentProps, {
					orientation: 'left',
					plain: true,
				});
			}
			return componentProps as Recordable;
		});

		function handleRules(): ValidationRule[] {
			const {
				rules: defRules = [],
				component,
				rulesMessageJoinLabel,
				label,
				dynamicRules,
				required,
			} = props.schema;

			// let count = 0;
			let labelRs = label;
			if (isFunction(label)) {
				labelRs = label(unref(getValues));
			}

			if (isFunction(dynamicRules)) {
				return dynamicRules(unref(getValues)) as ValidationRule[];
			}

			let rules: ValidationRule[] = cloneDeep(defRules) as ValidationRule[];
			const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } = props.formProps;

			const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel')
				? rulesMessageJoinLabel
				: globalRulesMessageJoinLabel;
			const defaultMsg = createPlaceholderMessage(component) + `${joinLabel ? labelRs : ''}`;

			function validator(rule: any, value: any) {
				// console.log(value, '<<<>>>');
				const msg = rule.message || defaultMsg;
				// if ((component === 'PictureCardUpload' || component === 'SimpleUpload') && !count && !value) {
				// 	count++;
				// 	return Promise.resolve();
				// } else
				if (value === undefined || isNull(value)) {
					// 空值
					return Promise.reject(msg);
				} else if (Array.isArray(value) && value.length === 0) {
					// 数组类型
					return Promise.reject(msg);
				} else if (typeof value === 'string' && value.trim() === '') {
					// 空字符串
					return Promise.reject(msg);
				} else if (
					typeof value === 'object' &&
					Reflect.has(value, 'checked') &&
					Reflect.has(value, 'halfChecked') &&
					Array.isArray(value.checked) &&
					Array.isArray(value.halfChecked) &&
					value.checked.length === 0 &&
					value.halfChecked.length === 0
				) {
					// 非关联选择的tree组件
					return Promise.reject(msg);
				}
				// count = 0;
				return Promise.resolve();
			}

			const getRequired = isFunction(required) ? required(unref(getValues)) : required;

			if ((!rules || rules.length === 0) && getRequired) {
				let ruleObj = { required: getRequired, validator };

				// 上传组件在onchange之前触发validator
				if (component === 'PictureCardUpload' || component === 'SimpleUpload') {
					Object.assign(ruleObj, { trigger: 'blur' });
				}
				rules = [ruleObj];
			}

			const requiredRuleIndex: number = rules.findIndex(
				rule => Reflect.has(rule, 'required') && !Reflect.has(rule, 'validator'),
			);

			if (requiredRuleIndex !== -1) {
				const rule = rules[requiredRuleIndex];
				const { isShow } = getShow();
				if (!isShow) {
					rule.required = false;
				}
				if (component) {
					if (!Reflect.has(rule, 'type')) {
						rule.type = component === 'InputNumber' ? 'number' : 'string';
					}

					rule.message = rule.message || defaultMsg;

					if (component.includes('Input') || component.includes('Textarea')) {
						rule.whitespace = true;
					}
					const valueFormat = unref(getComponentsProps)?.valueFormat;
					setComponentRuleType(rule, component, valueFormat);
				}
			}

			// Maximum input length rule check
			const characterInx = rules.findIndex(val => val.max);
			if (characterInx !== -1 && !rules[characterInx].validator) {
				rules[characterInx].message =
					rules[characterInx].message || `'字符数应小于' + ${[rules[characterInx].max] as Recordable} + '位'`;
			}
			return rules;
		}

		function renderLabelHelpMessage() {
			const { label, subLabel, helpMessage, helpComponentProps } = props.schema;
			let labelRs = label;
			if (isFunction(label)) {
				labelRs = label(unref(getValues));
			}
			const renderLabel = subLabel ? (
				<span>
					{labelRs} <span class="text-secondary">{subLabel}</span>
				</span>
			) : (
				labelRs
			);
			const getHelpMessage = isFunction(helpMessage) ? helpMessage(unref(getValues)) : helpMessage;
			if (!getHelpMessage || (Array.isArray(getHelpMessage) && getHelpMessage.length === 0)) {
				return renderLabel;
			}
			return (
				<span>
					{renderLabel}
					<BasicHelp placement="top" text={getHelpMessage} {...helpComponentProps} />
				</span>
			);
		}

		function renderComponent() {
			const { component, field, changeEvent = 'change', valueField, renderComponentContent } = props.schema;

			const eventKey = `on${upperFirst(changeEvent)}`;
			const isCheck = component && ['Switch', 'Checxbox'].includes(component);
			const { size, autoSetPlaceHolder } = props.formProps;
			const propsData: Recordable = {
				allowClear: true,
				size,
				getPopupContainer: (trigger: Element) => trigger.parentNode,
				...unref(getComponentsProps),
				disabled: unref(getDisable),
			};
			const on = {
				[eventKey]: (...args: Nullable<Recordable>[]) => {
					const [e] = args;
					if (propsData[eventKey]) {
						propsData[eventKey](...args);
					}
					const target = e ? e.target : null;
					const value = target ? (isCheck ? target?.checked : target?.value) : e;
					props.setFormModel(field, value);
				},
			};

			const Comp = componentMap.get(component) as ReturnType<typeof defineComponent>;

			const isCreatePlaceHolder = !propsData.disabled && autoSetPlaceHolder;
			if (isCreatePlaceHolder && component && component !== 'RangePicker') {
				propsData.placeholder = unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component);
			}
			propsData.codeField = field;
			propsData.formValues = unref(getValues);

			const bindValue: Recordable = {
				[valueField || (isCheck ? 'checked' : 'value')]: props.formModel[field],
			};

			const compAttr = {
				...propsData,
				...on,
				...bindValue,
			};

			if (!renderComponentContent) {
				return <Comp {...compAttr} />;
			}
			const compSlot = isFunction(renderComponentContent)
				? { ...renderComponentContent(unref(getValues)) }
				: { default: () => renderComponentContent };
			return <Comp {...compAttr}>{compSlot}</Comp>;
		}

		function renderItem() {
			const { colon: globalColon } = props.formProps as FormProps;
			const { field, itemProps, colon, component, suffix, slot, style } = props.schema as FormSchema;
			const { labelCol, wrapperCol } = unref(itemLabelWidthProp);
			if (component === 'Divider') {
				return (
					<Col span={24}>
						<Divider {...unref(getComponentsProps)}>{renderLabelHelpMessage()}</Divider>
					</Col>
				);
			}
			// TODO: render
			const getContent = () => {
				return slot ? getSlot(slots, slot, unref(getValues)) : renderComponent();
			};

			const showSuffix = !!suffix;
			const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix;

			const itemStyle = { 'margin-right': '20px', ...style };

			return (
				<Form.Item
					style={itemStyle}
					name={field}
					colon={globalColon || colon}
					{...itemProps}
					label={renderLabelHelpMessage()}
					rules={handleRules()}
					labelCol={labelCol}
					wrapperCol={wrapperCol}
				>
					<div class="flex items-center">
						<div style="flex: 1">{getContent()}</div>
						{showSuffix && <span class="form-item-suffix">{getSuffix}</span>}
					</div>
				</Form.Item>
			);
		}

		return () => {
			const { colProps = {}, colSlot, renderColContent, component } = props.schema;
			if (!componentMap.has(component)) {
				return null;
			}

			const { baseColProps = {} } = props.formProps;
			const realColProps = { ...baseColProps, ...colProps };
			const { isIfShow, isShow } = getShow();
			const values = unref(getValues);

			const getContent = () => {
				return colSlot
					? getSlot(slots, colSlot, values)
					: renderColContent
					? renderColContent(values)
					: renderItem();
			};

			return (
				isIfShow && (
					<Col {...realColProps} v-show={isShow}>
						{getContent()}
					</Col>
				)
			);
		};
	},
});
</script>
